﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;

namespace ERF
{
    /// <summary>
    /// Defines an object in 3D space by a set of vertices.
    /// </summary>
    public abstract class GeometricGameObject : GameObject
    {
        private VertexDeclaration vertexDeclaration;
        protected VertexBuffer vertexBuffer;
        protected IndexBuffer indexBuffer;
        protected int primitiveCount;
        protected EffectBase effect;
        protected PrimitiveType primitiveType;
        private ushort[] indices;
        private VertexPositionNormalTexture[] vertices;
        private Texture2D texture;
        private bool isTransparent;

        /// <summary>
        /// Fills the vertexbuffer with vertex data.
        /// </summary>
        private void SetVertexBuffer()
        {
            vertexBuffer = new VertexBuffer(CurrentGame.GraphicsDevice, typeof(VertexPositionNormalTexture), vertices.Length, BufferUsage.WriteOnly);
            vertexBuffer.SetData<VertexPositionNormalTexture>(this.vertices);
        }

        /// <summary>
        /// Fills the indexbuffer with index data.
        /// </summary>
        private void SetIndexBuffer()
        {
            indexBuffer = new IndexBuffer(CurrentGame.GraphicsDevice, typeof(ushort), indices.Length, BufferUsage.WriteOnly);
            indexBuffer.SetData<ushort>(this.indices);
        }

        /// <summary>
        /// Gets or sets the effect that this object uses to draw itself.
        /// </summary>
        public EffectBase Effect {
            get { return this.effect; }
            set { this.effect = value; }
        }

        /// <summary>
        /// Gets or sets the number of primitives that this object defines.
        /// </summary>
        public int PrimitiveCount
        {
            get { return primitiveCount; }
            set { primitiveCount = value; }
        }

        /// <summary>
        /// Gets or sets the type of the primitive that the vertices in this object define.
        /// </summary>
        public PrimitiveType PrimitiveType
        {
            get { return primitiveType; }
            set { primitiveType = value; }
        }

        /// <summary>
        /// Gets or sets the indices of this object.
        /// </summary>
        public ushort[] Indices
        {
            get { return indices; }
            protected set
            {
                if (this.indices != value)
                {
                    indices = value;
                    this.SetIndexBuffer();
                }
            }
        }

        /// <summary>
        /// Gets or sets the vertices of this object.
        /// </summary>
        public VertexPositionNormalTexture[] Vertices
        {
            get { return vertices; }
            protected set
            {
                if (this.vertices != value)
                {
                    this.vertices = value;
                    this.SetVertexBuffer();
                }
            }
        }

        /// <summary>
        /// Gets or sets the texture this object uses.
        /// </summary>
        protected Texture2D Texture
        {
            get { return this.texture; }
            set
            {
                if (this.texture != value)
                    this.texture = value;
            }
        }

        /// <summary>
        /// Gets or sets a value denoting if this object should be drawn transparent or not.
        /// </summary>
        public bool IsTransparent {
            get { return this.isTransparent; }
            set { this.isTransparent = value; }
        }

        /// <summary>
        /// Creates a new GeometricGameObject.
        /// </summary>
        /// <param name="game">The game that this object belongs to.</param>
        public GeometricGameObject(ERF.ERFGame game) : base(game)
        {
            this.vertexDeclaration = new VertexDeclaration(CurrentGame.GraphicsDevice, VertexPositionNormalTexture.VertexElements);
        }

        /// <summary>
        /// Draws this object as an opaque figure.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void DrawSolid(GameTime gameTime)
        {
            if (!this.isTransparent) {
                Camera camera = base.CurrentGame.Camera;
                CurrentGame.GraphicsDevice.VertexDeclaration = this.vertexDeclaration;
                CurrentGame.GraphicsDevice.Vertices[0].SetSource(this.vertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);
                CurrentGame.GraphicsDevice.Indices = this.indexBuffer;

                if (base.renderWithoutDepthBuffer)
                    base.CurrentGame.GraphicsDevice.RenderState.DepthBufferWriteEnable = false;

                effect.World = this.World;
                effect.View = camera.View;
                effect.Projection = camera.Projection;
                effect.ViewDirection = camera.Position - camera.Target;
                effect.EyePosition = camera.Position;
                base.OnSetEffectParameters(this.effect);

                effect.Begin();
                foreach (EffectTechnique technique in effect.Techniques) {
                    foreach (EffectPass pass in technique.Passes) {
                        pass.Begin();

                        CurrentGame.GraphicsDevice.DrawIndexedPrimitives(this.primitiveType, 0, 0, this.vertices.Length, 0, this.primitiveCount);

                        pass.End();
                    }
                }
                effect.End();

                if (base.renderWithoutDepthBuffer)
                   base.CurrentGame.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
                
            }
        }

        /// <summary>
        /// Draws this object as a transparent figure.
        /// </summary>
        /// <param name="gameTime"></param>
        public override void DrawTransparent(GameTime gameTime) {
            if (this.isTransparent) {
                Camera camera = base.CurrentGame.Camera;
                CurrentGame.GraphicsDevice.VertexDeclaration = this.vertexDeclaration;
                CurrentGame.GraphicsDevice.Vertices[0].SetSource(this.vertexBuffer, 0, VertexPositionNormalTexture.SizeInBytes);
                CurrentGame.GraphicsDevice.Indices = this.indexBuffer;

                effect.GraphicsDevice.RenderState.AlphaBlendEnable = true;
                effect.GraphicsDevice.RenderState.SourceBlend = Blend.SourceAlpha;
                effect.GraphicsDevice.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
                effect.GraphicsDevice.RenderState.DepthBufferWriteEnable = false;

                effect.World = this.World;
                effect.View = camera.View;
                effect.Projection = camera.Projection;
                effect.ViewDirection = camera.Position - camera.Target;
                effect.EyePosition = camera.Position;
                base.OnSetEffectParameters(this.effect);

                effect.Begin();
                foreach (EffectTechnique technique in effect.Techniques) {
                    foreach (EffectPass pass in technique.Passes) {
                        pass.Begin();

                        CurrentGame.GraphicsDevice.DrawIndexedPrimitives(this.primitiveType, 0, 0, this.vertices.Length, 0, this.primitiveCount);

                        pass.End();
                    }
                }
                effect.End();

                effect.GraphicsDevice.RenderState.AlphaBlendEnable = false;
                effect.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
            }
        }

        public override void Update(GameTime gameTime, ref Matrix world) {
            base.UpdateWorld(ref world);

            base.Update(gameTime, ref base.world);
        }

        public override void Dispose()
        {
            this.vertexBuffer.Dispose();
            this.indexBuffer.Dispose();
            this.vertexBuffer = null;
            this.indexBuffer = null;
        }
    }
}
